<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>CandyJs js DOC</title>
    <meta name="keywords" content="CandyJs MVC Framework Restful API">
    <meta name="description" content="一个面向对象的高效 node.js MVC and REST 框架">

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" >
    <style>
    blockquote {font-size: 14px;}
    blockquote.bg-danger {
        border-left-color: #cc3322;
    }
    blockquote.bg-info {
        border-left-color: #1b809e;
    }
    footer {
        background-color: #222;
        color: #fff;
        padding: 20px 0;
    }
    </style>
</head>
<body>
<header>
    <nav class="navbar navbar-inverse">
        <div class="container">
            <a target="_blank" class="navbar-brand" href="https://github.com/douyu-beijing/candyjs">CandyJs</a>
        </div>
    </nav>
</header>

<div style="margin-top: 60px" class="container">
    <div class="row">
        <div class="col-md-9">
            <h1>
            一个面向对象的高效 MVC and REST 框架
            </h1>
            <p>
                <code>CandyJs</code>是一个健壮可扩展的 Web 框架，它提供了一套优雅的编写代码的规范，使得编写 Web 应用变得得心应手。
            </p>
            <p>
                <code>CandyJs</code>实现了一套<code>动态路由</code>规范，您不需要提前注册所需要的路由，只需要输入网址<code>CandyJs</code>会自己找到路由对应的组件。
            </p>
            <p>
                <code>CandyJs</code>实现了正则路由的合并，多个正则路由我们会将其合成一个大路由，避免了路由逐个匹配带来的巨大性能损失
            </p>

            <h1 class="page-header"><a href="./index_en.html">英文</a></h1>

            <h1 class="page-header">入门</h1>

            <h4><b>安装 candyjs</b></h4>
<pre><code>
$ mkdir demo
$ cd demo
$ npm init
$ npm install candyjs
</code></pre>

            <h1 class="page-header" id="firstrun"><b>第一次运行程序</b></h1>
            <p>安装完 candyjs 后，需要创建一个具体项目来编写业务逻辑</p>
            <p>使用如下命令创建一个基本应用框架</p>
            <p>本例输出一个 MVC 模式 Hello word 程序，如果只想要写一些接口程序，请看<a href="#restful">这里</a></p>
            
            <pre><code>$ ./node_modules/.bin/_candy</code></pre>

            <p>这样就会创建一个简单的目录结构</p>
<pre><code>
PROJECT_NAME
|
|- index.js
|
|- app
|  |
|  |-- controllers 普通控制器目录
|      |
|      |-- index
|      |   |
|      |   |-- IndexController.js
|      |
|   -- views
|      |
|      |-- index
|      |   |
|      |   |-- index.html
</code></pre>

            <p>进入 PROJECT_NAME 目录，启动程序</p>
            <pre><code>$ node index.js</code></pre>
            <p>访问程序</p>
            <pre><code><a href="http://localhost:2333" target="_blank">http://localhost:2333</a></code></pre>

            <h1 class="page-header" id="appstruct">应用结构</h1>

            <h4><b>一个比较完整的应用目录结构如下</b></h4>
<pre><code>
PROJECT_NAME
|
|- index.js
|
|- node_modules 依赖模块目录
|
|- public 目录 一般存放静态文件
|
|- app 项目目录
|  |
|  |-- controllers 普通控制器目录
|      |
|      |-- user 用户组目录
|      |   |
|      |   |-- IndexController.js  - host:port/user/index 可以访问到该类
|      |   |-- OtherController.js  - host:port/user/other 可以访问到该类
|      |
|      |-- goods 商品组目录
|      |   |
|      |   |-- IndexController.js  - host:port/goods/index 可以访问到该类
|      |   |-- OtherController.js  - host:port/goods/other 可以访问到该类
|      |
|   -- views 普通控制器模板目录
|      |
|      |-- user 用户组模板 对应上面用户组
|      |   |
|      |   |-- index.html
|      |   |-- other.html
|      |
|   -- goods 商品组模板
|      |   |
|      |   |-- index.html
|      |   |-- other.html
|      |
|   -- modules 模块
|      |
|      |-- reg
|      |   |
|      |   |-- controllers 模块控制器目录 其下无子目录
|      |   |   |
|      |   |   |-- IndexController.js
|      |   |
|      |   |-- views 模块模板目录
|      |   |   |
|      |   |   |-- index.html
|      |   |
|      |   |-- 其他目录
|      |
|   -- runtime 缓存目录
|
</pre></code>

            <h4><b>入口脚本<code>index.js</code></b></h4>
            <p>
                入口脚本是应用启动流程中的第一环，一个应用只有一个入口脚本。
                入口脚本包含启动代码，程序启动后就会监听客户端的连接
            </p>
            <p>
                入口脚本主要完成以下工作
                <ul>
                    <li>加载应用配置</li>
                    <li>启动应用</li>
                    <li>注册各种需要组件</li>
                </ul>
            </p>
<pre><code>
var CandyJs = require('candyjs');
var App = require('candyjs/web/Application');

var app = new App({
    'id': 1,

    // 定义调试应用
    'debug': true,

    // 定义应用路径
    'appPath': __dirname + '/app',

    // 注册模块
    'modules': {
        'bbs': 'app/modules/bbs'
    },

    // 配置日志
    'log': {
        'targets': {
            'file': {
                'classPath': 'candy/log/file/Log'
            }
        }
    }

});
new CandyJs(app).listen(2333, function(){
    console.log('listen on 2333');
});
</pre></code>

            <h4><b>应用</b></h4>
            <p>
                <code>CandyJs</code>包含两种应用：<code>Web 应用</code>和<code>Rest 应用</code>
            </p>

            <h4><b>应用属性</b></h4>
            <p>在入口文件中可以传入各种参数，这些参数最终会被赋值到应用对象上</p>

            <h6><b>必要属性</b></h6>
            <ul>
                <li>
                    <p><code>candy/web/Application.id</code> 该属性用来标识唯一应用</p>
                </li>
                <li>
                    <p><code>candy/web/Application.appPath</code> 该属性用于指明应用所在的目录</p>
                </li>
            </ul>

            <h6><b>重要属性</b></h6>
            <ul>
                <li>
                    <p><code>candy/web/Application.routesMap</code> 用来自定义路由处理程序</p>
<pre><code>
// account 路由使用 app/controllers/user/IndexController 做处理 并传入了一个参数 property
'account': {
    'classPath': 'app/controllers/user/IndexController',
    'property': 'value'
}
</code></pre>
                </li>
                <li>
                    <p><code>candy/web/Application.modules</code> 用来注册应用模块</p>
<pre><code>
// 注册一个 bbs 模块
'modules': {
    'bbs': 'app/modules/bbs'
}
</code></pre>
                </li>
                <li>
                    <p><code>candy/web/Application.encoding</code> 项目编码方式</p>
                </li>
                <li>
                    <p><code>candy/web/Application.debug</code> 是否开启调试</p>
                </li>
            </ul>

            <h4><b>自定义属性</b></h4>
            <p>其他在入口文件中传入的参数都会作为自定义参数传入应用对象</p>

            <h1 class="page-header" id="controller"><b>应用控制器</b></h1>
            <p>控制器是<code>MVC</code>模式中的一部分，是继承<code>candy/core/Controller</code>类的对象，负责处理请求和生成响应</p>
            <p>业务逻辑一般都应该出现在控制器中，包括但不限于处理用户传递的数据，计算数据等</p>

            <h4><b>动作</b></h4>
            <p>控制器由<code>动作</code>组成，它是执行终端用户请求的最基础的单元，一个控制器有且只有一个入口动作叫做<code>run</code>，入口动作在控制器被创建后会自动运行</p>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    // 入口动作
    run(req, res) {
        res.end('hello');
    }

}

module.exports = IndexController;
</pre></code>

            <h4><b>动作切面</b></h4>
            <p>
                如果控制器从<code>candy/web/Controller</code>继承而来，
                那么就可以使用动作切面在控制器的动作执行前后实现一些业务逻辑
            </p>
            <blockquote class="bg-danger">
                <p>由于前期设计导致动作切面作用不是很大，从 4.3.0 开始，我们对动作切面进行了重构</p>
                <p>并且对方法名进行了调整</p>
                <ul>
                    <li><code>beforeActionCall() 更名为 beforeAction()</code></li>
                    <li><code>afterActionCall() 更名为 afterAction()</code></li>
                </ul>
                <p><code>beforeAction(actionEvent)</code> 方法必须同步返回一个布尔类型的值，以表明程序是否继续向后执行。<code>true</code>表示告诉程序向下执行，其他返回值则会阻止程序运行，这给一些需要做验证或者其他预处理的业务提供了便利性</p>
            </blockquote>

<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    // 这里演示了如何阻止程序的默认执行
    // 改为手动控制程序走向
    // 由于修改了程序执行逻辑，控制器的默认入口 run() 就不需要了
    // 4.7.0 及之前通过 return false 阻止程序继续运行
    // 4.8.0 及之后 通过对 actionEvent.valid = false 阻止程序继续运行
    beforeAction(actionEvent) {
        actionEvent.valid = false;
        
        setTimeout(() => {
            this.myRun(req, res);
        }, 1000);

        // 阻止程序的运行
        // return false;
    }

    afterAction(req, res) {
        console.log('afterActionCall')
    }

    // 入口
    myRun(req, res) {
        res.end('hello');
    }

}

module.exports = IndexController;
</pre></code>


            <h1 class="page-header" id="filter"><b>动作过滤器</b></h1>
            <p>动作过滤器是一种特殊的行为类，用于过滤用户的请求，如果项目中使用到了<code>动作切面</code>，那么可以考虑是否可以替换为<code>过滤器</code></p>
            <p><code>candyjs</code>提供的过滤器可以从<code>core/ActionFilter.js</code>找到，该类主要用于在控制器的动作执行前进行一些预处理操作</p>

            <h4><b>过滤器的使用</b></h4>
            <h6><b>1. 编写过滤器</b></h6>
            <p>使用过滤器，首先必须编写过滤器，过滤器需要从<code>candyjs/core/ActionFilter</code>类继承</p>
            <p>自定义过滤器根据具体需求可以选择实现<code>beforeAction()</code>或<code>afterAction()</code>方法</p>
            <p><code>beforeAction()</code>会在控制器的动作执行前运行，<code>afterAction()</code>会在控制器的动作执行后运行</p>
            <p>下面的过滤器代码简单实现了一个为接口请求添加跨域头的功能，并实现了过滤器的<code>beforeAction()</code>方法</p>
<pre><code>
// app/filters/Cors.js
const Candy = require('candyjs/Candy');
const ActionFilter = require('candyjs/core/ActionFilter');

module.exports = class Cors extends ActionFilter {
    constructor() {
        super();

        this.cors = {
            // 允许访问该资源的 origin
            'Access-Control-Allow-Origin': '*',
            // 允许使用的请求方法
            'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS',
            // 允许请求中使用的头信息
            'Access-Control-Allow-Headers': '',
            // 响应的有效时间为 秒
            'Access-Control-Max-Age': 86400,
            // 如果出现在预检请求的响应中 表示实际请求是否可以使用 credentials
            'Access-Control-Allow-Credentials': true
        };
    }

    /**
     * 实现过滤器前置方法，用于添加 http header 头
     */
    beforeAction(actionEvent) {
        let request = actionEvent.request;
        let response = actionEvent.response;

        let headers = this.generateHeaders(request);
        for(let k in headers) {
            response.setHeader(k, headers[k]);
        }

        if('OPTIONS' === request.method) {
            actionEvent.valid = false;

            response.writeHead(200, 'OK');
            response.write('');
            response.end();
        }
    }

    generateHeaders(request) {
        let ret = {};

        // oirigin
        if(undefined !== request.headers['origin'] && undefined !== this.cors['Access-Control-Allow-Origin']) {
            if(this.cors['Access-Control-Allow-Origin'].indexOf(request.headers['origin']) >= 0) {
                ret['Access-Control-Allow-Origin'] = request.headers['origin'];
            }

            // allow origin 为 * 和 credentials 不能同时出现
            if('*' === this.cors['Access-Control-Allow-Origin']) {
                if(this.cors['Access-Control-Allow-Credentials']) {
                    throw new Error('Allowing credentials for wildcard origins is insecure');
                } else {
                    ret['Access-Control-Allow-Origin'] = '*';
                }
            }
        }

        // 客户端请求方法
        if(undefined !== request.headers['access-control-request-method']) {
            ret['Access-Control-Allow-Methods'] = this.cors['Access-Control-Allow-Methods'];
        }

        // 允许的 header
        if(undefined !== this.cors['Access-Control-Allow-Headers']) {
            ret['Access-Control-Allow-Headers'] = this.cors['Access-Control-Allow-Headers'];
        }

        // 认证
        if(undefined !== this.cors['Access-Control-Allow-Credentials']) {
            ret['Access-Control-Allow-Credentials'] = this.cors['Access-Control-Allow-Credentials'] ? 'true' : 'false';
        }

        if('OPTIONS' === request.method && undefined !== this.cors['Access-Control-Max-Age']) {
            ret['Access-Control-Max-Age'] = this.cors['Access-Control-Max-Age'];
        }

        return ret;
    }
}
</pre></code>

            <h6><b>2. 使用过滤器</b></h6>
            <p>使用过滤器很简单，只需要在控制器中添加<code>behaviors()</code>方法即可</p>
<pre><code>
class IndexController extends Controller {
    // 使用过滤器
    // 过滤器配置以数组形式返回
    // 'cors' 表示过滤器的名字
    // 'app/filters/Cors' 表示过滤器的类路径 这里表示 app/filters/Cors.js 类
    behaviors() {
        return [
            ['cors', 'app/filters/Cors']
        ];
    }

    run() {
        // 此控制器动作执行前会先运行 behaviors() 方法指定的过滤器
    }
}
</pre></code>

            <h1 class="page-header" id="router"><b>MVC 路由</b></h1>
            <p>路由是<code>MVC</code>模式中的一部分，实现控制器寻址</p>
            <p><code>MVC</code>模式中，每一个路由会对应到一个控制器文件</p>

            <h4><b>路由与控制器</b></h4>
            <p>一般一个路由对应一个控制器 路由有如下两种格式</p>
            <ol>
                <li><p><code>route_prefix[/controllerId]</code></p></li>
                <li><p><code>moduleId[/controllerId]</code></p></li>
            </ol>
            <ul>
                <li>
                    <p>直接访问域名 <a>http://localhost/</a></p>
                    <p>会默认映射到 <code>app/controllers/index/IndexControlelr</code> 控制器</p>
                </li>
                <li>
                    <p>访问 <a>http://localhost/user</a></p>
                    <p>首先会查找是否存在 <code>user</code> 模块</p>
                    <p>如果存在，会映射到 <code>app/modules/user/controllers/IndexController</code></p>
                    <p>如果不存在，则会映射到 <code>app/controllers/user/IndexController</code></p>
                </li>
                <li>
                    <p>访问 <a>http://localhost/user/profile</a></p>
                    <p>首先会查找是否存在 <code>user</code> 模块</p>
                    <p>如果存在，会映射到 <code>app/modules/user/controllers/ProfileController</code></p>
                    <p>如果不存在，则会映射到 <code>app/controllers/user/ProfileController</code></p>
                </li>
                <li>
                    <p>访问 <a>http://localhost/user/profile/settings</a></p>
                    <p>首先会查找是否存在 <code>user</code> 模块</p>
                    <p>如果存在，会映射到 <code>app/modules/user/controllers/SettingsController</code></p>
                    <p>如果不存在，则会映射到 <code>app/controllers/user/profile/SettingsController</code></p>
                </li>
            </ul>

            <p>控制器查找顺序 优先查找模块下的控制器<code>模块控制器 --> 普通控制器</code></p>

            <h4><b>自定义路由配置</b></h4>
            <p><code>CandyJs</code>中可以自定义路由的处理程序，这里通过<code>routesMap</code>来实现</p>
<pre><code>
var App = require('candyjs/web/Application');

var app = new App({
    id: 'myapp',

    routesMap: {
        // 比如访问 http://www.somedomain.com/account
        // 这时候 account 路由将使用 app/controllers/user/IndexController 做处理
        // 并将 IndexController 的 foo 成员变量赋值为 123
        'account': {
            'classPath': 'app/controllers/user/IndexController',
            'foo': '123'
        }
    }
});
</pre></code>

            <h4><b>路由拦截</b></h4>
            <p>很多时候，网站会需要进行维护，这时候需要关闭网站，<code>candyjs</code>提供了路由拦截功能，使得网站维护变得简单</p>
<pre><code>
var App = require('candyjs/web/Application');

var app = new App({
    id: 'myapp',

    // 这条配置使得所有请求都会被转发到 app/Intercept 去处理
    'interceptAll': 'app/Intercept',
});

// Intercept.js
class Intercept {
    run(req, res) {
        res.end('intercepted');
    }
}
module.exports = Intercept;
</pre></code>

            <h1 class="page-header" id="model"><b>模型</b></h1>
            <p>模型是<code>MVC</code>模式中的一部分，是代表业务数据和规则的对象</p>
            <p>模型并不能直接操作数据库，它只提供数据存储和校验，数据库操作需要借助数据库操作类实现</p>
            <blockquote class="bg-info">
                <p>数据库操作 <a target="_blank" href="https://github.com/douyu-beijing/candyjs-db-mysql2">从这里了解详情</a></p>
                <p>模型示例 <a target="_blank" href="https://github.com/douyu-beijing/candyjs-examples/tree/master/model">从这里了解详情</a></p>
            </blockquote>
            
            <h4><b>使用模型</b></h4>
            <p>使用模型需要以下几个步骤</p>
            <ol>
                <li>定义模型</li>
                <li>填充数据</li>
                <li>校验数据</li>
            </ol>
            <h6><b>1. 定义模型</b></h6>
            <p>在<code>CandyJs</code>中，模型由<code>属性（ attributes ）</code>和<code>规则（ rules ）</code>组成</p>
            <p>属性是一个 JSON 对象，其中的键名一般与数据库定义的字段一一对应，规则是一个数组，配置了对属性的验证规则</p>
            <p>每一个验证规则由三部分组成：规则验证器（由 rule 字段指定，支持对象实例、类配置以及别名路径格式）、需要验证的属性列表（由 attributes 字段指定）、错误时的提示信息（由 messages 字段指定）</p>
            <p>如下是一个用户信息模型</p>
<pre><code>
const Model = require('candyjs/model/Model');

module.exports = class UserModel extends Model {
    constructor() {
        super();

        // 模型属性
        this.attributes = {
            user_name: '',
            password: '',
            confirming: '',
            email: ''
        };
        
        this.attributesMap = {
            user_name: 'name'
        };
    }

    // 模型规则
    rules() {
        return [
            {
                rule: 'candy/model/RequiredValidator',
                attributes: ['user_name', 'password', 'email'],
                messages: ['用户名不能为空', '密码不能为空', '邮箱不能为空']
            },
            {
                rule: 'candy/model/EqualValidator',
                attributes: ['password', 'confirming'],
                messages: ['两次密码不一致']
            },
            {
                rule: 'candy/model/EmailValidator',
                attributes: ['email'],
                messages: ['邮箱格式不正确']
            }
        ];
    }
}
</code></pre>

            <h6><b>2. 填充数据</b></h6>
            <p>当有了定义好的模型后，接下来就是为模型填充数据，数据一般由用户通过前台页面以表单形式发送到后台，这里以控制器处理为例</p>
<pre><code>
const UserModel = require('../../models/UserModel');

module.exports = class PostController {

    run(req, res) {
        let model = new UserModel();
        
        // 数据填充
        model.fill(req);

        // todo ...
        
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
        res.end('');
    }

}
</code></pre>
            <blockquote class="bg-info">
                <p>数据填充时，有可能前台表单的字段名称和模型中的属性名称不一致，这时候数据将会填充失败。</p>
                <p>可以在模型定义时增加<code>attributesMap</code>字段，就如上面定义模型的例子那样：<code>this.attributesMap = { user_name: 'name' };</code></p>
                <p>模型使用的是 user_name，而前台表单使用的是 name，通过此种映射方式实现字段不一致时的对应</p>
            </blockquote>
            
            <h6><b>3. 校验数据</b></h6>
            <p>模型提供了<code>validate()</code>方法，调用该方法，模型会执行<code>rules()</code>配置的校验规则对属性进行校验，并返回一个布尔值代表成功或者失败</p>
            <p>如果验证失败，可以通过<code>getErrors()</code>获取错误信息，该方法返回一个数组，包含了所有的错误信息</p>
<pre><code>
const UserModel = require('../../models/UserModel');

module.exports = class PostController {

    run(req, res) {
        let model = new UserModel();
        
        // 数据填充
        model.fill(req);

        // 执行校验
        let valid = model.validate();
        let errors = model.getErrors();

        let data = JSON.stringify({
            status_code: valid ? 0 : 1,
            messages: errors
        });

        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
        res.end(data);
    }

}
</code></pre>

            <h4><b>系统内置验证器</b></h4>
            <p><code>CandyJs</code>内置了几个常用的验证器，如上述示例中<code>rule: 'candy/model/EmailValidator'</code>，它表示被验证的属性必须是一个邮箱格式</p>
            <p>内置验证器如下</p>
            <ul>
                <li><code>BooleanValidator</code></li>
                <li><code>EmailValidator</code></li>
                <li><code>EqualValidator</code></li>
                <li><code>RequiredValidator</code></li>
                <li><code>StringValidator</code></li>
            </ul>
            
            <h4><b>自定义验证器</b></h4>
            <p>由于业务的不同，系统内置验证器无法满足所有需求，这时候就可以使用自定义验证器</p>
            <p>自定义验证器比较简单，只需要从系统的<code>Vakudatir</code>继承，并实现其中的<code>validate()</code>方法即可</p>
            <blockquote class="bg-info">
                <p>需要注意的是，<code>validate()</code>方法的返回值是字符串，代表了验证失败的错误信息。如果没有错误，需要返回空字符</p>
            </blockquote>
<pre><code>
// 定义一个验证数字范围的验证器
const Validator = require('candyjs/model/Validator');

class MyNumberValidator extends Validator {
    constructor() {
        super();

        this.max = 100;
        this.min = 1;
    }

    validate(attributeName, attributeValue) {
        let info = this.getMessage(attributeName);

        if(attributeValue < this.min || attributeValue > this.max) {
            return '' === info ? 'number is invalid' : info;
        }

        return '';
    }
}
</code></pre>

            <h4><b>使用自定义验证器</b></h4>
<pre><code>
const Model = require('candyjs/model/Model');

class UserModel extends Model {
    constructor() {
        super();

        ...
    }

    rules() {
        return [
            {
                rule: new MyNumberValidator(),
                attributes: ['age'],
                messages: ['年龄不合法']
            }
        ];
    }
}
</code></pre>
            

            <h1 class="page-header" id="view"><b>视图</b></h1>
            <p>视图是<code>MVC</code>模式中的一部分 它用于给终端用户展示页面</p>

            <h4><b>模板引擎</b></h4>
            <blockquote class="bg-info">
                <code>CandyJs</code>提供了<code>@candyjs/template-hbs</code>来负责模板渲染，<a target="_blank" href="https://github.com/douyu-beijing/candyjs-template-hbs">查看详情</a>
            </blockquote>

            <h4><b>其他方式使用模板引擎</b></h4>
            <p>得益于<code>CandyJs</code>灵活的架构设计，使得模板引擎使用非常便捷，这里有如下几种方式：</p>
            <ul>
                <li>全局配置方式</li>
                <li>局部注入方式</li>
                <li>嵌入式</li>
            </ul>
            <p>下面将使用<code>Handlebars</code>对以上方式进行逐个讲解</p>

            <h6><b>全局配置方式</b></h6>
<pre><code>
// 全局配置方式是使用 candyjs 的别名系统实现的
// 这里的代码可以从源代码 examples 目录中找到

// 1. 在 app/libs 目录中建立一个模板引擎文件 CandyTemplate.js
const fs = require('fs');
const Handlebars = require('handlebars');
// 加载系统视图类
const View = require('candyjs/web/View');

class CandyTemplate extends View {
    constructor(context) {
        super(context);
        this.handlebars = Handlebars.create();
    }

    // 模板引擎必须实现这个方法，因为它是渲染模板的入口
    renderFile(file, parameters) {
        fs.readFile(file, 'UTF-8', (err, template) => {
            let compiled = this.handlebars.compile(template);
            this.context.response.end( compiled(parameters) );
        });
    }
}
module.exports = CandyTemplate;

// 2. 经过第 1 步，我们的模板引擎就开发完了，是不是很简单
// 接下来在入口注册我们编写的模板引擎
const App = require('candyjs/web/Application');
const app = new App({
    'id': 'template_test',

    // 配置模板引擎
    'defaultView': 'app/libs/CandyTemplate',

    'appPath': __dirname + '/app'
});
new CandyJs(app).listen(2333, function(){
    console.log('listen on 2333');
});

// 3. 准备模板 html
&lt;html>
&lt;body>
&lt;ul>
{{#each list}}
    &lt;li>&lt;a href="/user?uid={{ id }}">{{ name }}&lt;/a>&lt;/li>
{{/each}}
&lt;/ul>
&lt;/body>
&lt;/html>

// 4. 在控制器中使用模板引擎渲染页面
const Controller = require('candyjs/web/Controller');
const User = require('somepath/models/User');

class IndexController extends Controller {
    run(req, res) {
        this.fetchList(res);
    }

    async fetchList(res) {
        const user = new User();
        // 这里 data 是一个用户数组 [{id: xxx, name: xxx}]
        let data = await user.getUserList();

        // 可以获取到模板引擎实例
        // 具体使用方式请参考 handlebars 模板引擎官方文档
        // const handlebars = this.getView().handlebars;
        // handlebars.todo

        // 这里的 render 将使用我们制定的模板引擎渲染页面
        this.render('index', {
            list: data
        });
    }
}
</code></pre>

            <h6><b>局部注入方式</b></h6>
<pre><code>
// 1. 局部注入方式第 1 步也需要编写我们的模板引擎，参考全局配置方式

// 2. 在控制器中动态注入模板引擎
const Controller = require('candyjs/web/Controller');
const User = require('somepath/models/User');
const CandyTemplate = require('somepath/CandyTemplate');

class IndexController extends Controller {
    run(req, res) {
        this.fetchList(res);
    }

    async fetchList(res) {
        const user = new User();
        let data = await user.getUserList();

        // 动态注入模板引擎
        this.setView(new CandyTemplate(this.context));
        this.render('index', {
            list: data
        });
    }
}

module.exports = IndexController;
</code></pre>

            <h6><b>嵌入式</b></h6>
<pre><code>
// 这种方式比较灵活，不需要编写模板引擎

const Controller = require('candyjs/web/Controller');
const Handlebars = require('handlebars');
const User = require('somepath/models/User');

class IndexController extends Controller {
    run(req, res) {
        this.fetchList(res);
    }

    async fetchList(res) {
        const user = new User();
        let data = await user.getUserList();

        this.getView().getViewContent('index', (err, str) => {
            // 直接使用模板引擎对内容进行编译并输出
            let compiled = Handlebars.compile(str);

            res.end( compiled({ list: data }) );
        });
    }
}
module.exports = IndexController;
</code></pre>

            <h4><b>控制器层的视图 API</b></h4>
            <p>
                如果用户的控制器从<code>candy/web/Controller</code>继承而来
                那么可以在控制器中使用<code>getView()</code>方法来获取视图类实例
            </p>
            <p>视图类提供了如下<code>API</code>供用户使用</p>
            <ul>
                <li><code>findViewFile(view)</code> 用于获取一个视图文件的绝对路径</li>
                <li><code>getViewContent(view, callback)</code> 用于读取一个视图文件的内容</li>
            </ul>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    run(req, res) {
        this.getView().getViewContent('index', (err, str) => {
            res.end(str);
        });
    }

}

module.exports = IndexController;
</code></pre>

            <h1 class="page-header" id="module"><b>模块</b></h1>
            <p>
                模块是独立的软件单元，体量较小，<code>由模型、视图、控制器</code>和其他组件组成。
            </p>
            <p>
                终端用户可以访问在应用主体中已注册的模块的控制器，<code>CandyJs</code>在解析路由时优先查找模块中有没有满足条件的控制器
            </p>
            <p>如果项目中某个功能相对独立，与项目关联不大，随时准备删除或加入进来，那么可以考虑将其放入模块系统</p>
            <blockquote class="bg-info">
                注意 和普通项目目录不同的是 模块中的<code>控制器和视图</code>没有子目录
            </blockquote>

            <h4><b>创建模块</b></h4>
            <p>在应用目录的<code>modules</code>目录中建立单独目录创建模块 如下</p>
<pre><code>
modules 模块目录
    |
    |-- newyearactive 新年活动页面
    |   |
    |   |-- controllers 模块控制器目录
    |   |   |
    |   |   |-- IndexController.js
    |   |
    |   |-- views 模块视图目录
    |   |   |
    |   |   |-- index.html
    |   |
    |   |-- 其他目录
</code></pre>

            <h4><b>注册模块</b></h4>
            <p>创建完成的模块还不能被系统识别 需要手动注册一下</p>
<pre><code>
var CandyJs = require('candyjs');
var App = require('candyjs/web/Application');

new CandyJs(new App({
    ...

    // 注册模块
    // 这样路由为 /newyearactive 时会优先匹配到该模块
    'modules': {
        'newyearactive': 'app/modules/newyearactive'
    },

    ...

})).listen(2333, function(){
    console.log('listen on 2333');
});
</code></pre>

            <h1 class="page-header" id="component"><b>组件 & 行为</b></h1>

            <h4><b>组件</b></h4>
            <p>组件是实现<code>行为 (behavior) 事件 (event)</code>的基类，如果一个类从<code>candy/core/Component</code>或其子类继承而来，那么这个类就拥有组件的特性</p>
            <p>
                组件是<code>candy/core/Component</code>或其子类的实例
            </p>
            <p>
                <code>CandyJs</code>中<code>candy/web/Controller</code>类继承自<code>candy/core/Component</code>，所以控制器具有组件的特性
            </p>
            <p>行为类一般与组件类同时使用</p>

            <h4><b>行为</b></h4>
            <p>
                行为是<code>candy/core/Behavior</code>类或其子类的实例，具有如下作用
            </p>
            <ul>
                <li>一个行为类可以用于在不改变原组件代码的情况下增强其功能</li>
                <li>行为类能够监听组件的事件并作出响应，这对于拦截框架的核心事件，并对其做出处理提供了方便</li>
            </ul>

            <h4><b>事件</b></h4>
            <p><code>CandyJs</code>中实现了一个观察者模式</p>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    constructor(context) {
        super(context);

        this.on('myevent', function() {
            console.log('myevent fired');
        });
    }

    run(req, res) {
        this.trigger('myevent');

        res.end('hello');
    }

}

module.exports = IndexController;
</code></pre>

            <h4><b>行为的使用</b></h4>

            <blockquote class="bg-danger">
                从 4.4.0 开始，我们对行为进行了重构，去除了作用不大的方法注入，保留了事件监听
            </blockquote>

            <h6><b>定义行为</b></h6>
            <p>要定义行为，可以通过继承<code>candy/core/Behavior</code>或其子类来建立一个类</p>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Behavior = Candy.include('candy/core/Behavior');

// 行为类
class MyBehavior extends Behavior {
    constructor() {
        super();
    }

    // 监听控制器的 customEvent 事件
    // 由于一个事件可以有多个处理程序 为保证顺序 这里必须使用数组
    // 格式为 [行为名, 行为处理器]
    events() {
        return [
            ['customEvent', (e) => {
                e.result = 'data processed by behavior';
            }],
            ['customEvent2', (e) => {
                e.result += '--process2';
            }]
        ];
    }
}

module.exports = MyBehavior;
</code></pre>

            <p>以上代码定义了行为类<code>MyBehavior</code>并监听了一个自定义事件<code>customEvent</code>

            <h6><b>附加行为到组件</b></h6>
            <p>可以通过<code>静态配置</code>或者<code>动态方法</code>形式附加行为到组件</p>

            <p>要使用静态配置附加行为，只需要重写组件类的<code>behaviors()</code>方法即可。<code>behaviors()</code>方法应该返回行为配置列表</p>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    // 重写方法
    behaviors() {
        return [
            ['myBehavior', new MyBehavior()]
        ];
    }

    run(req, res) {
        let data = {result: ''};
        this.trigger('customEvent', data);

        // 卸载行为
        this.detachBehavior('myBehavior');
        return data.result;
    }

}

module.exports = IndexController;
</code></pre>

            <p>要使用动态方法附加行为，在组件里调用<code>attachBehavior()</code>方法即可</p>
<pre><code>
'use strict';

var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');

class IndexController extends Controller {

    constructor(context) {
        super(context);

        // 动态附加行为 行为里面会监听 customEvent 事件
        this.attachBehavior('myBehavior', new MyBehavior());
    }

    run(req, res) {
        let data = {result: ''};
        this.trigger('customEvent', data);
        this.trigger('customEvent2', data);

        this.detachBehavior('myBehavior');
        return data.result;
    }

}

module.exports = IndexController;
</code></pre>


            <h1 class="page-header" id="midware"><b>中间件</b></h1>
            <p>中间件是处理请求的第一个环节，可以对请求做过滤处理并调用下一个中间件</p>
            <blockquote class="bg-danger">
                <p>使用过多的中间件并不是一个好的选择，因为有些程序并不需要中间件处理，好的做法是在控制器中处理相关逻辑</p>
            </blockquote>
            <p><code>CandyJs</code>中的中间件需要通过<code>Hook</code>进行注册，每一个中间件是一个可以接受三个参数的函数</p>
<pre><code>
// 入口文件
var CandyJs = require('candyjs');
var Candy = require('candyjs/Candy');
var App = require('candyjs/web/Application');

var Hook = Candy.include('candy/core/Hook');

Hook.addHook((req, res, next) => {
    // do something
    next();
});
Hook.addHook((req, res, next) => {
    setTimeout(() => {
        next();
    }, 2000);
});

new CandyJs(new App({

    ...

})).listen(2333, function(){
    console.log('listen on 2333');
});
</code></pre>

            <blockquote class="bg-info">
                <code>CandyJs</code>暂时只提供了一个处理静态资源的中间件
                理论上<code>CandyJs</code>兼容任何<code>express</code>的中间件
            </blockquote>

            <h1 class="page-header" id="static"><b>静态资源</b></h1>
            <p>
                <code>CandyJs</code>默认是不处理静态资源的，需要使用中间件
            </p>
<pre><code>
// 入口文件
var CandyJs = require('candyjs');
var Candy = require('candyjs/Candy');
var App = require('candyjs/web/Application');

var Hook = Candy.include('candy/core/Hook');
var R = Candy.include('candy/midwares/Resource');

// 使用内置静态服务管理静态资源
// 比如 public 目录下有个 js 文件夹保存了各种业务文件
// 4.14.0 前使用以下方式
Hook.addHook(new R(__dirname + '/public').serve());
// 4.14.0 后使用以下方式
// Hook.addHook(R.serve(__dirname + '/public'));

new CandyJs(new App({

    ...

})).listen(2333, function(){
    console.log('listen on 2333');
});


// 之后可以在页面中访问静态资源 真实路径是 /public/js/lib.js
&lt;script src="/js/lib.js">&lt;/script>
</code></pre>

            <h1 class="page-header" id="url"><b>URI & URL 类</b></h1>
            <p>
                <code>candy/web/URI 及 candy/web/URL</code>类提供了对 uri 和 url 操作的方法
            </p>

            <h4><b>candy/web/URI</b></h4>
            <ul>
                <li><code>parseUrl()</code> 解析 url</li>
            </ul>
<pre><code>
var URI = Candy.include('candy/web/URI');

var uri = new URI();

/*
{
    source: 'http://xxx.com:8080/abc?q=1#anchor',
    scheme: 'http',
    user: undefined,
    password: undefined,
    host: 'xxx.com',
    port: '8080',
    path: '/abc',
    query: 'q=1',
    fragment: 'anchor'
}
*/
uri.parseUrl('http://xxx.com:8080/abc?q=1#anchor');
</code></pre>

            <h4><b>candy/web/URL</b></h4>
            <ul>
                <li><code>getReferer()</code> 获取先前网页的地址</li>
                <li><code>getHostInfo()</code> 获取 URI 协议和主机部分</li>
                <li><code>getCurrent()</code> 获取当前网址 不包含锚点部分</li>
                <li><code>to(url[, params = null])</code> 创建一个 url</li>
            </ul>
<pre><code>
var URL = Candy.include('candy/web/URL');

var url = new URL(req);

// return scheme://host/index/index
url.to('index/index');

// return scheme://host/index/index?id=1#anchor
url.to('index/index', {id: 1, '#': 'anchor'})
</code></pre>

            <h1 class="page-header" id="reqres"><b>请求与响应</b></h1>
            <p>
                <code>CandyJs</code>提供了处理请求和响应的类
                <code>candy/http/Request</code>和<code>candy/http/Response</code>
            </p>

            <h4><b>HTTP 请求 candy/http/Request 类</b></h4>
            <p>
                用于处理 http 请求
                该对象提供了对诸如<code>请求参数 HTTP头 cookies</code>等信息的访问方法
            </p>
            <p>
                <code>candy/http/Request</code>类提供了一组实例和静态方法来操作需要的数据
            </p>
            <ul>
                <li><code>getQueryString(param)</code> 实例方法获取 GET 请求参数</li>
                <li><code>getParameter(param)</code> 实例方法获取 POST 请求参数</li>
                <li><code>getCookie(name)</code> 实例方法获取 COOKIE</li>
                <li><code>getHeaders()</code> 获取 http headers 集合</li>            </ul>
            <blockquote class="bg-danger">
                <code>CandyJs</code>中使用 getParameter() 获取 POST 参数需要依赖第三方解析 body 的中间件，否则将反回 null
            </blockquote>
<pre><code>
var Request = Candy.include('candy/http/Request');
var request = new Request(req);
var id = request.getQueryString('id');
...
</code></pre>
</p>

<p>
<pre><code>
const bodyParser = require('body-parser');

Hook.addHook(bodyParser.json());
</code></pre>

            <h4><b>HTTP 响应 candy/http/Response 类</b></h4>
            <p>
                主要用户向客户端输出响应消息
            </p>
            <p>
                <code>candy/http/Response</code>类提供了一组实例和静态方法来操作响应数据
            </p>
            <ul>
                <li><code>setStatusCode(value[, text])</code> 设置 http status code</li>
                <li><code>setHeader(name, value)</code> 设置 header</li>
                <li><code>setContent(content)</code> 设置实体内容</li>
                <li><code>setCookie(name, value[, options])</code> 设置一条 cookie</li>
                <li><code>send([content])</code> 发送 HTTP 响应到客户端</li>
                <li><code>redirect(url[, statusCode = 302])</code> 页面重定向</li>
            </ul>

            <h6><b>使用 response 输出内容</b></h6>
<pre><code>
var Response = Candy.include('candy/http/Response');
var response = new Response(res);
response.setContent('some data from server');
response.send();
</code></pre>
</p>

            <h6><b>使用 response 重定向</b></h6>
<p>
<pre><code>
var Response = Candy.include('candy/http/Response');
var response = new Response(res);
response.redirect('http://foo.com');
</code></pre>

            <h1 class="page-header" id="helper"><b>助手类</b></h1>
            <p>助手类封装了一些常用操作</p>

            <h4><b>文件助手类<code>FileHelper</code></b></h4>
            <ul>
                <li><code>getDirname(dir)</code> 获取路径的 dir 部分</li>
                <li><code>normalizePath(path[, directorySeparator = '/'])</code> 正常化一个路径</li>
                <li><code>createDirectory(dir[, mode = 0o777[, callback = null]])</code> 异步创建文件夹</li>
                <li><code>createDirectorySync(dir[, mode = 0o777])</code> 同步创建文件夹</li>
            </ul>

            <h4><b>字符串助手类<code>StringHelper</code></b></h4>
            <ul>
                <li><code>nIndexOf(str, find, n)</code> 查找某字符串在另一个字符串中第 N 次出现的位置</li>
                <li><code>trimChar(str, character)</code> 删除两端字符</li>
                <li><code>lTrimChar(str, character)</code> 删除左侧字符</li>
                <li><code>rTrimChar(str, character)</code> 删除右侧字符</li>
                <li><code>ucFirst(str)</code> 首字母大写</li>
                <li><code>htmlSpecialChars(str[, flag = 0[, doubleEncode = true]])</code> 转化特殊 html 字符到实体</li>
                <li><code>filterTags(str[, allowed = ''])</code> 过滤 html 标签</li>
            </ul>

            <h4><b>时间助手类<code>TimeHelper</code></b></h4>
            <ul>
                <li><code>format(formats[, timestamp = Date.now()])</code> 格式化时间</li>
            </ul>

<pre><code>
var Response = Candy.include('candy/helpers/FileHelper');
var StringHelper = Candy.include('candy/helpers/StringHelper');
var TimeHelper = Candy.include('candy/helpers/TimeHelper');

// return /a/c
var path = FileHelper.normalizePath('/a/./b/../c');

// return &amp;lt;script&amp;gt;
var str = StringHelper.htmlSpecialChars('&lt;script&gt;');

// return abcxyz
var strTag = StringHelper.filterTags('&lt;a&gt;abc&lt;/a&gt;xyz');

// 格式化当前时间 return xxxx-xx-xx xx:xx:xx
var time = TimeHelper.format('y-m-d h:i:s');
</code></pre>

            <h1 class="page-header" id="alias"><b>别名系统</b></h1>
            <p>为了方便类的管理，实现自动加载、初始化等，<code>CandyJs</code>提供了一套别名系统</p>
            <p><b>别名是一个以<code>@</code>符号开头的字符串，每一个别名对应一个真实的物理路径</b></p>
            <p><code>CandyJs</code>中加载类以及创建类的实例都是用的别名</p>

            <h4><b>系统内置别名</b></h4>
            <ul>
                <li><code>@candy</code> 指向 CandyJs 目录</li>
                <li><code>@app</code> 项目目录 由<code>appPath</code>在入口文件中指定</li>
                <li><code>@runtime</code> 缓存目录 默认指向<code>@app/runtime</code></li>
                <li><code>@root</code> 网站根目录</li>
            </ul>

            <h4><b>类的加载及实例化</b></h4>
            <h6><b>类的加载</b></h6>
            <p><code>CandyJs</code>中可以方便的使用别名来加载类</p>
<pre><code>
var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');
</code></pre>
            <p>上面的<code>Candy.include('candy/web/Controller');</code>可以实现加载系统的<code>Controller</code>类</p>
            <p>其中开头的<code>candy</code>就是一个系统内置别名。<code>CandyJs</code>在解析路径的时候，首先会尝试将路径的开始部分当做一个别名来解析</p>
            <blockquote class="bg-danger">
                <p>虽然<code>Candy.include</code>可以很方便的加载一个类，但是这种方式加载的类是无法识别类型的，所以也就不会有代码提示功能</p>
                <p>对于需要代码提示的人推荐使用 require 进行加载<code>const Controller = require('candyjs/web/Controller')</code></p>
            </blockquote>
            

            <h6><b>类的实例化</b></h6>
            <p><code>CandyJs</code>中很多类都是使用<code>Candy.createObject()</code>来进行实例化的，他的强大之处在于可以识别别名，自动加载和初始化一个类</p>

<pre><code>
// 1. 以字符串方式加载并实例化类，这种方式比较简单
Candy.createObject('candy/utils/LinkedQueue', '还可以传入一些构造函数参数');

// 2. 通过配置进行类的加载和实例化，这种方式除了可以传入构造函数参数外，还能在配置中传入一些配置作为动态属性
Candy.createObject({
    classPath: 'candy/utils/LinkedQueue',
    myProp1: '111',
    myProp2: '222'
}, '构造函数参数');
</code></pre>

            <h4><b>自定义别名</b></h4>
            <p>用户可以自定义别名</p>
<pre><code>
// 注册别名
Candy.setPathAlias('@lib', '/home/www/library');

// 加载并创建 /home/www/library/MyClass 类
var obj = Candy.createObject('lib/MyClass');
</code></pre>

            <h1 class="page-header" id="restful"><b>RESTful</b></h1>
            <blockquote class="bg-danger">
                在 4.0.0 之前此功能一直是实验性质的
            </blockquote>

            <h4><b>用法</b></h4>
            <ul>
                <li><code>get(route, handler)</code></li>
                <li><code>post(route, handler)</code></li>
                <li><code>put(route, handler)</code></li>
                <li><code>delete(route, handler)</code></li>
                <li><code>patch(route, handler)</code></li>
                <li><code>head(route, handler)</code></li>
                <li><code>options(route, handler)</code></li>
            </ul>
<pre><code>
var CandyJs = require('candyjs');
var App = require('candyjs/rest/Application');

var app = new App({
    id: 1,
    appPath: __dirname + '/app',
    debug: true
});

// 简单的请求
app.get('/homepage', (req, res) => {
    res.end('homepage');
});

// 含参数的请求
// id 参数会转译成正则表达式
app.get('/posts/{id}', (req, res, params) => {
    res.end(params.id);
});

// 限制参数类型
// 由于转译字符的解析问题 类型限制需要传入两个反斜线
app.get('/user/{id:\\d+}', (req, res, params) => {
    res.end(params.id);
});

// 使用一个类处理回调
// 这个请求会使用 app/api/Demo 类的 index 方法处理
app.get('/xyz', 'app/api/Demo@index');

var candyJs = new CandyJs(app);
candyJs.listen('2333', () => {
    console.log('listen on 2333')
});
</code></pre>

            <h4><b>RESTful 中的路由问题</b></h4>
            <p>
                RESTful 中的路由是用正则表达式来实现的，它可以实现非常灵活的路由配置
                但是相对于 MVC 中的路由性能要差 ( mvc 模式中的路由不是用正则实现的 )
            </p>

            <h4><b>MVC 模式接口</b></h4>
            <p>使用 mvc 模式也可以写接口程序，而且代码更具有组织性，更便于维护，但是缺少灵活性，传递参数没那么便利。参考项目的<code>examples/api_mvc</code>了解详情</p>

            <h1 class="page-header" id="log"><b>日志</b></h1>
            <p>
                <code>CandyJs</code>提供了日志处理功能，目前只支持文件日志
            </p>

            <h4><b>使用日志</b></h4>
            <p>使用日志功能前，需要在入口文件注册，<code>CandyJs</code>可以同时配置多种日志，只需要在<code>targets</code>项目中配置即可</p>
<pre><code>
'log': {
    'targets': {
        // 文件日志
        'file': {
            // 必须参数，表示类的路径，支持别名路径
            'classPath': 'candy/log/file/Log',
            // 日志输出目录
            'logPath': __dirname + '/logs',
            // 文件名
            'logFile': 'system.log',
            'maxFileSize': 10240  // 10 MB
        }
    },
    'flushInterval': 10  // 每 10 次向文件写一次 log
}
</code></pre>

<pre><code>
var CandyJs = require('candyjs');

var log = CandyJs.getLogger();
log.error('This is a error message');
log.flush();  // 写入硬盘
</code></pre>

            <p>
                <code>targets</code>用于配置日志处理程序，现在只支持文件日志，所以上面配置了<code>file</code>字段，<code>candyjs</code>可以同时配置多个<code>targets</code>，只需要在和<code>file</code>字段平级的地方再添加一个日志配置，那么每次这日志，新配置的日志也会被执行。
            </p>
            <p><code>flushInterval</code>表示日志写入硬盘的频率。由于 IO 操作比较耗时，<code>candyjs</code>出于优化考虑，并不会每次调用日志接口就写日志，而是先缓存起来，在某一时刻一同写入。这里指定每调用 10 次日志接口向硬盘同步一次</p>
            <p>用户手动调用<code>flush()</code>也会触发同步日志到硬盘的操作</p>

            <h4><b>日志接口</b></h4>
            <ul>
                <li><code>error(message)</code> 记录错误日志</li>
                <li><code>warning(message)</code> 记录警告日志</li>
                <li><code>info(message)</code> 记录信息日志</li>
                <li><code>trace(message)</code> 记录追踪日志</li>
                <li><code>flush()</code> 输出日志</li>
            </ul>

            <h4><b>扩展日志</b></h4>
            <blockquote class="bg-info">
                <code>CandyJs</code>暂时只提供了文件日志功能，如果想实现诸如数据库日志等的功能必须自己进行日志扩展。
                <code>CandyJs</code>完善的代码设计使得进行日志扩展非常容易，只需要让扩展的日志类继承<code>candy/log/ILog</code>并实现其中的<code>flush()</code>方法即可
            </blockquote>
<pre><code>
// 1. 定义日志类
var Candy = require('candyjs/Candy');  // 助手类
var ILog = Candy.include('candy/log/ILog');

// app/libs/MyDbLog.js
class MyDbLog extends ILog {
    flush(messages) {
        console.log(messages);
    }
}

// 2. 在入口文件配置自己的日志类
var App = require('candyjs/web/Application');

var app = new App({
    'id': 1,
    'appPath': __dirname + '/app',
    // 配置日志
    'log': {
        'targets': {
            'dblog': {
                'classPath': 'app/libs/MyDbLog'
            }
        },
        'flushInterval': 10
    }
});


// 3. 使用日志
var CandyJs = require('candyjs');  // 框架入口类

var log = CandyJs.getLogger();
log.error('This is a error message');
log.flush();
</code></pre>

            <h4><b>第三方日志</b></h4>
            <p>如果上述方式不能满足你的需求，你也可以使用第三方日志，如<code>log4js</code>，只需要按照第三方库的文档使用即可</p>

            <h1 class="page-header" id="cache"><b>缓存</b></h1>
            <p>
                <code>CandyJs</code>提供了数据缓存处理的功能 目前只支持文件缓存
            </p>

            <h4><b>使用缓存</b></h4>
            <p>使用缓存功能前 需要在入口文件注册</p>
<pre><code>
'cache': {
    'file': {
        'classPath': 'candy/cache/file/Cache',
        'cachePath': '...'
    }
}
</code></pre>

            <p><code>file</code>用于指定使用文件缓存</p>

            <h4><b>缓存接口</b></h4>
            <ul>
                <li><code>setSync(key, value, duration)</code> 同步写入缓存</li>
                <li><code>set(key, value, duration, callback)</code> 异步写入缓存</li>
                <li><code>getSync(key)</code> 同步读取缓存</li>
                <li><code>get(key, callback)</code> 异步读取缓存</li>
                <li><code>deleteSync(key)</code> 同步删除缓存</li>
                <li><code>delete(key, callback)</code> 异步删除缓存</li>
            </ul>
<pre><code>
var Candy = require('candyjs/Candy');
var Cache = Candy.include('candy/cache/Cache');

var c = Cache.getCache('file');

// 同步
c.setSync('key', 'value');
var data = c.getSync('key');

// 异步
async function getSetData() {
    await c.set('key2', 'value2');

    let data = await c.get('key2');
}
</code></pre>

            <blockquote class="bg-info">
                <code>CandyJs</code>暂时只提供了文件缓存功能 如果想实现诸如数据库缓存等的功能必须自己进行缓存扩展
                <code>CandyJs</code>完善的代码设计使得进行缓存扩展非常容易 只需要让扩展的缓存类继承<code>candy/cache/ICache</code>并实现其中的方法即可
            </blockquote>
            
            <h1 class="page-header" id="i18n"><b>国际化</b></h1>
            <blockquote class="bg-info">
                <p>示例 <a target="_blank" href="https://github.com/douyu-beijing/candyjs-examples/tree/master/action-filter">从这里了解详情</a></p>
            </blockquote>
            
            <h1 class="page-header" id="exception"><b>异常处理</b></h1>
            <p><code>CandyJs</code>提供了方便的异常处理机制，系统默认使用<code>candy/web/ExceptionHandler</code>处理异常，用户可以通过继承重写该类的<code>handlerException()</code>方法来实现自定义输出，或者提供一个带有<code>handlerException()</code>方法的自定义类来处理错误</p>
<pre><code>
// index.js
const app = new App({
    'id': 1,
    'appPath': __dirname + '/app',
    // 定义异常处理类
    'exceptionHandler': 'app/ExceptionHandler'
});

// app/ExceptionHandler.js
module.exports = class ExceptionHandler {
    handlerException(res, exp) {
        res.end('server error');
    }
}
</code></pre>
            
            <h1 class="page-header" id="uploader"><b>文件上传</b></h1>
            <blockquote class="bg-info">
                <code>CandyJs</code>提供了<code>@candyjs/uploader</code>来负责上传功能，<a target="_blank" href="https://github.com/douyu-beijing/candyjs-uploader">查看详情</a>
            </blockquote>
            
            <h1 class="page-header" id="database"><b>数据库</b></h1>
            <ul>
                <li><a target="_blank" href="https://www.npmjs.com/package/@candyjs/db-mysql2">MYSQL</a></li>
                <li><a target="_blank" href="https://www.npmjs.com/package/@candyjs/db-sqlite3">SQLite</a></li>
            </ul>

            <h1 class="page-header" id="session"><b>Session</b></h1>
            <blockquote class="bg-info">
                <code>CandyJs</code>提供了
                <a target="_blank" href="https://www.npmjs.com/package/@candyjs/session-cookie">@candyjs/session-cookie</a>
                来实现基于 cookie 的 session
            </blockquote>
            <p>此外<code>CandyJs</code>还支持使用第三方 session 库，比如<code>express-session</code></p>
<pre><code>
// index.js
const CandyJs = require('candyjs');
const App = require('candyjs/web/Application');
const Hook = require('candyjs/core/Hook');

const session = require('express-session')

Hook.addHook(session({
    secret: 'some strings'
}));

new CandyJs(new App({
    'id': 1,
    'debug': true,
    'appPath': __dirname + '/app'

})).listen(2333, function(){
    console.log('listen on 2333');
});


// 控制器中使用
const Controller = require('candyjs/web/Controller');

class IndexController extends Controller {
    run(req, res) {
        let n = req.session.views || 0;
        req.session.views = ++n;

        res.write('views: ' + n);
        res.end();
    }
}
</code></pre>


            <h1 class="page-header" id="ts"><b>TypeScript</b></h1>
            <p>TypeScript 是一种由微软开发的自由和开源的编程语言，它是 JavaScript 的一个超集。</p>
            <p>
                <code>CandyJs</code>同样支持 typescript 项目以及使用 `tsc` 命令编译项目，但是需要安装<code>@candyjs/tswrapper</code>模块
            </p>
            <p>参考<a href="https://gitee.com/candyjs/candyjs-examples" target="_blank">https://gitee.com/candyjs/candyjs-examples</a>这里的 ts-* 或者 typescript 目录示例</p>
            
            <p>或者参考<a href="https://github.com/candyframework/candyjs-examples" target="_blank">https://github.com/candyframework/candyjs-examples</a>这里的 ts-* 或者 typescript 目录示例</p>

        </div>
        <div class="col-md-3" style="position: relative;">
            <ul id="rightnav" class="nav nav-pills nav-stacked" style="background-color: #f5f5f5">
                <li><a href="javascript:;">入门</a></li>
                <li><a href="#firstrun">第一次运行程序</a></li>
                <li><a href="#appstruct">应用结构</a></li>
                <li><a href="#controller">控制器</a></li>
                <li><a href="#filter">动作过滤器</a></li>
                <li><a href="#router">MVC 路由</a></li>
                <li><a href="#model">模型</a></li>
                <li><a href="#view">视图</a></li>
                <li><a href="#module">模块</a></li>
                <li><a href="#component">组件 & 行为</a></li>
                <li><a href="#midware">中间件</a></li>
                <li><a href="#static">静态资源</a></li>
                <li><a href="#url">URI & URL 类</a></li>
                <li><a href="#reqres">请求与响应</a></li>
                <li><a href="#helper">助手类</a></li>
                <li><a href="#alias">别名系统</a></li>
                <li><a href="#restful">RESTful</a></li>
                <li><a href="#log">日志</a></li>
                <li><a href="#cache">缓存</a></li>
                <li><a href="#i18n">国际化</a></li>
                <li><a href="#exception">异常处理</a></li>
                <li><a href="#uploader">文件上传</a></li>
                <li><a href="#database">数据库</a></li>
                <li><a href="#session">Session</a></li>
                <li><a href="#ts">TypeScript</a></li>
            </ul>
        </div>
    </div>
</div>

<footer>
    <div class="container">
        <h4>&copy; CandyJs</h4>
    </div>
</footer>

<script src="./js.js"></script>

<a href="https://www.revolvermaps.com/livestats/5ixry4498hq/" style="display: none" title="台湾、香港等是中国领土不可分割的部分">
    <img src="//rf.revolvermaps.com/h/m/a/0/ff0000/128/0/5ixry4498hq.png" width="256" height="128" alt="Map" style="border:0;">
</a>
</body>
</html>
